package com.emc.esu.api.rest
{
	import com.emc.esu.api.EsuCallbackState;
	import com.emc.esu.api.EsuError;
	
	import flash.utils.setTimeout;
	
	import mx.logging.ILogger;
	import mx.logging.Log;
	
	/**
	 * Implements a helper class to manage retrying a function call.  Includes a filter to determine
	 * which errors are fatal and should not be retried.
	 */
	public class RestRetryWrapper{
	    private var logger:ILogger;
		
		
		private var _thisPtr:Object;
		private var _func:Function;
		private var _callback:Function;
		private var _args:Array;
		private var _maxRetryCount:int;
		private var _retryCount:int = 0;
		private var _retryDelay:int;
		private var	_id:String;
		private var _state:Object;
		private static var serial:int = 0;
		
		public function RestRetryWrapper( thisPtr:Object, func:Function, args:Array, 
			callback:Function, state:Object, maxRetryCount:int, retryDelay:int ) {
			
			this.logger = Log.getLogger("RestRetryWrapper");
			_id = "RestRetryWrapper-" + (serial++);
			
			this._thisPtr = thisPtr;
			this._func = func;
			this._args = args;
			this._callback = callback;
			this._maxRetryCount = maxRetryCount;
			this._retryDelay = retryDelay;
			this._state = state;
		}
		
		/**
		 * Executes the retryable function
		 */
		public function execute():void {
			// Generate a new callback function and append to args array.
			_args.push( retryCallback, _state );
			
			// Execute the function
			logger.debug( _id + " executing initial " + _func );
			_func.apply( _thisPtr, _args );
		}
		
		/**
		 * This is the retry callback that determines whether the function
		 * call needs to be retried.
		 */
		public function retryCallback( state:EsuCallbackState ):void {
			logger.debug( _id + " complete.  Successful? " + state.wasSuccessful() );
			if( state.wasSuccessful() ) {
				// Execute the real callback.
				_callback( state );
			} else {
				var error:EsuError = state.getError();
				logger.debug( _id + " request failed.  code=" + error.getCode() + " message=" + error.message );
				if( isFatalError( error ) ) {
					// Error is fatal.  Don't retry.
					_callback( state );
				} else {
					// Retry the function
					_retryCount++;
					if( _retryCount >= _maxRetryCount ) {
						logger.debug( _id + ": out of retries.  Failing" );
						_callback( state );
					} else {
						if( _retryDelay > 0 ) {
							logger.debug( _id + ": Delaying " + _retryDelay + "ms before retry" );
							setTimeout( executeRetry, _retryDelay );
						} else {
							executeRetry();
						}
					}
				}
			}
		}
		
		private function executeRetry():void {
			logger.debug( _id + ": executing retry " + _retryCount + " of " + _func );
			_func.apply( _thisPtr, _args );			
		}
		
		private function isFatalError( error:EsuError ):Boolean {
			logger.debug( _id + ": isFatalError: " + error.getCode() );
			switch( error.getCode() ) {
				case 404:
					// Not found
					return true;
				case 403:
					// Not authorized
					return true;
				case 1032:
					// Signature failure
					return true;
				default:
					return false;
			}
		}
	}
}